/*
 * Copyright 2011 Kim Lindhardt Madsen
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package dk.lindhardt.gwt.geie.server;

import dk.lindhardt.gwt.geie.shared.*;

import java.io.*;
import java.text.DecimalFormat;
import java.text.ParseException;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * User: AnAmuser
 * Date: 24-06-11
 * <p/>
 * Class for building {@link dk.lindhardt.gwt.geie.shared.TableLayout} from a csv
 */
public class CSV2TableLayout {

   private String separator = ",";
   private String charset = "utf-8";
   private String newLine = System.getProperty("line.separator");

   private Logger logger = Logger.getLogger("CSV2TableLayout");
   private InputStream is;

   private List<SimpleDateFormat> dateFormats;
   private List<DecimalFormat> numberFormats;

   /**
    * New instance of the csv parser. Uses default separator <code>","</code>, charset <code>UTF-8</code> and this
    * systems line separator.
    * @param is input stream to read csv from
    */
   public CSV2TableLayout(InputStream is) {
      dateFormats = new ArrayList<SimpleDateFormat>();
      numberFormats = new ArrayList<DecimalFormat>();
      this.is = is;
   }

   /**
    * New instance of the csv parser. Uses default separator <code>","</code>, charset <code>UTF-8</code> and this
    * systems line separator.
    * @param file to read csv from
    */
   public CSV2TableLayout(File file) throws FileNotFoundException {
      this(new FileInputStream(file));
   }

   /**
    * Builds table layout from csv file. The returned table has Excel structure.
    * @return TableLayout
    */
   public TableLayout build() {
      TableLayout layout = new TableLayout();
      CellFormatKey defaultKey = layout.addCellFormat("default", new CellFormat());
      try {
         String csvString = convertStreamToString(is);
         List<List<String>> lines = getCsvStructure(csvString);
         for (int r = 0; r < lines.size(); r++) {
            List<String> line = lines.get(r);
            for (int c = 0; c < line.size(); c++) {
               String text = line.get(c);
               Position position = new Position(r, c);
               layout.addCell(createCell(text, position, defaultKey, layout));
            }
         }
         return layout;
      } catch (IOException ioe) {
         logger.log(Level.SEVERE, "Error reading CSV file", ioe);
      }
      return null;
   }

   public String convertStreamToString(InputStream is) throws IOException {
      if (is != null) {
         Writer writer = new StringWriter();

         char[] buffer = new char[1024];
         try {
            Reader reader = new BufferedReader(
                  new InputStreamReader(is, charset));
            int n;
            while ((n = reader.read(buffer)) != -1) {
               writer.write(buffer, 0, n);
            }
         } finally {
            is.close();
         }
         return writer.toString();
      } else {
         return "";
      }
   }

   private Cell createCell(String text, Position position, CellFormatKey defaultKey, TableLayout layout) {
      Date date = null;
      for (SimpleDateFormat dateFormat : dateFormats) {
         try {
            date = dateFormat.parse(text);
         } catch (ParseException e) {
            continue;
         }
         if (date != null) {
            CellFormat cellFormat = new CellFormat();
            String pattern = dateFormat.toPattern();
            cellFormat.setPattern(pattern);
            CellFormatKey cellFormatKey = layout.addCellFormat("Date " + pattern, cellFormat);
            return new DateCell(date, position, cellFormatKey);
         }
      }

      Number number = null;
      for (DecimalFormat numberFormat : numberFormats) {
         ParsePosition pos = new ParsePosition(0);
         number = numberFormat.parse(text, pos);
         if (pos.getIndex() < text.length() - 1) {
            continue;
         }
         if (number != null) {
            CellFormat cellFormat = new CellFormat();
            String pattern = numberFormat.toPattern();
            cellFormat.setPattern(pattern);
            cellFormat.setAlignment(CellFormat.Alignment.RIGHT);
            CellFormatKey cellFormatKey = layout.addCellFormat("Number " + pattern, cellFormat);
            return new NumberCell(number.doubleValue(), position, cellFormatKey);
         }
      }

      return new LabelCell(text, position, defaultKey);
   }

   private List<List<String>> getCsvStructure(String csvString) {
      List<List<String>> csvStructure = new ArrayList<List<String>>();
      String[] lines = csvString.split(newLine);
      Pattern csvPattern = Pattern.compile("((?:[^\"" + separator + "]|(?:\"(?:\\\\{2}|\\\\\"|[^\"])*?\"))*)");
      for (String line : lines) {
         List<String> cells = new ArrayList<String>();
         Matcher matcher = csvPattern.matcher(line);

         boolean useNextText = true;
         while (matcher.find()) {
            String cell = matcher.group(1);
            // The following logic is just needed because it is a fucked pattern, but it's easier than write a working pattern.
            if (cell != null && cell.length() == 0 && !useNextText) {
               useNextText = true;
            } else if (cell != null && cell.length() == 0 && useNextText) {
               useNextText = true;
               cells.add("");
            } else {
               // Remove quotes
               cell = cell.trim();
               if (cell.startsWith("\"") && cell.endsWith("\"")) {
                  cell = cell.substring(1, cell.length() - 1);
               }
               cell = cell.replaceAll("\"\"", "\"");
               cells.add(cell);

               useNextText = false;
            }
         }
         csvStructure.add(cells);
      }
      return csvStructure;
   }

   /**
    * @param charset the encoding
    */
   public void setCharset(String charset) {
      this.charset = charset;
   }

   /**
    * @param separator between cells
    */
   public void setSeparator(String separator) {
      this.separator = separator;
   }

   /**
    * @param newLine sets the new line character to break lines on
    */
   public void setNewLine(String newLine) {
      this.newLine = newLine;
   }

   /**
    * @param dateFormat Adds date format for parsing csv file
    */
   public void addDateFormat(SimpleDateFormat dateFormat) {
      dateFormats.add(dateFormat);
   }

   /**
    * @param numberFormat Adds number format for parsing csv file
    */
   public void addNumberFormat(DecimalFormat numberFormat) {
      numberFormats.add(numberFormat);
   }

}
